#include "track.h"
#include <cstring>
#include "util.h"

using namespace std;
using namespace MidiFile;

//------------------------------------------------------------------------------
// Constructores y destructor
//------------------------------------------------------------------------------

Track::Track(std::ifstream &file) throw (FileException, std::bad_alloc)
{
    read(file);
}

Track::Track(const Track &other) throw (std::bad_alloc)
{
    nBytes = other.nBytes;
    nTicks = other.nTicks;

    for (list<Event*>::const_iterator it = other.events.begin(); it != other.events.end(); it++)
        events.push_back(new Event(*(*it)));

    iterator = events.begin();
}

Track::~Track() throw ()
{
    while (!events.empty()) {
        delete events.front();
        events.pop_front();
    }
}

//------------------------------------------------------------------------------
// Propiedades
//------------------------------------------------------------------------------

Event & Track::getNextEvent() throw (InvalidException)
{
    if (iterator == events.end())
        throw InvalidException();

    return **iterator++;
}

//------------------------------------------------------------------------------
// Metodos
//------------------------------------------------------------------------------

void Track::read(std::ifstream &file) throw (FileException, std::bad_alloc)
{
    char header[4];
    bool endOfTrack = false;
    Event *event;

    unsigned char prevStatus = 0;
    unsigned char status;

    EventType eventType;
    MetaEventType metaEventType;
    int deltaTime;
    char midiChannel;
    char param1;
    char param2;
    char *data = NULL;
    char metaEvent;
    int dataLength;
    int dataAllocLength = 0;

    if (!file)
        throw FileException("El descriptor de archivo no esta listo");

    nTicks = 0;

    file.read(header, 4);

    if (memcmp(header, "MTrk", 4) != 0)
        throw FileException("Fichero incompatible. Se esperaba \"MTrk\"");

    file.read(reinterpret_cast<char*> (&nBytes), 4);
    MIDIFILE_SWAP32(nBytes);

    do {
        deltaTime = readVariableLength(file);
        file.read(reinterpret_cast<char*> (&status), 1);

        // Lectura de datos

        switch (status) {
        case Meta:
            file.get(metaEvent);

        case SysEx:
        case SysExCont:
            dataLength = readVariableLength(file);
            extendAllocation(data, dataLength, dataAllocLength);
            file.read(data, dataLength);
            break;

        default:

            switch (status & 0xF0) {
            case NoteOn:
            case NoteOff:
            case NoteAftertouch:
            case Controller:
            case PitchBend:
                file.read(reinterpret_cast<char*> (&param1), 1);
                file.read(reinterpret_cast<char*> (&param2), 1);
                prevStatus = status;
                break;

            case ProgramChange:
            case ChannelAftertouch:
                file.read(reinterpret_cast<char*> (&param1), 1);
                param2 = 0;
                prevStatus = status;
                break;

            default:
                param1 = status;
                status = prevStatus;

                switch (status & 0xF0) {
                case NoteOn:
                case NoteOff:
                case NoteAftertouch:
                case Controller:
                case PitchBend:
                    file.read(reinterpret_cast<char*> (&param2), 1);

                default:
                    ;
                }
            }

            midiChannel = status & 0xF;
        }

        // Clasificacion

        switch (status) {
        case Meta:
            eventType = Meta;

            switch (metaEvent) {
            case SequenceNumber:
                metaEventType = SequenceNumber;
                break;
            case Text:
                metaEventType = Text;
                break;
            case CopyrightNotice:
                metaEventType = CopyrightNotice;
                break;
            case SequenceName:
                metaEventType = SequenceName;
                break;
            case InstrumentName:
                metaEventType = InstrumentName;
                break;
            case Lyrics:
                metaEventType = Lyrics;
                break;
            case Marker:
                metaEventType = Marker;
                break;
            case CuePoint:
                metaEventType = CuePoint;
                break;
            case OnChannelPrefix:
                metaEventType = OnChannelPrefix;
                break;
            case OnPortPrefix:
                metaEventType = OnPortPrefix;
                break;
            case EndOfTrack:
                metaEventType = EndOfTrack;
                endOfTrack = true;
                break;
            case SetTempo:
                metaEventType = SetTempo;
                break;
            case SMTPOffset:
                metaEventType = SMTPOffset;
                break;
            case TimeSignature:
                metaEventType = TimeSignature;
                break;
            case KeySignature:
                metaEventType = KeySignature;
                break;
            case SequencerSpecific:
                metaEventType = SequencerSpecific;
                break;

            default:
                throw FileException("Fichero incomatible. Meta-evento desconocido");
            }

            break;

        case SysEx:
            eventType = SysEx;
            break;
        case SysExCont:
            eventType = SysExCont;
            break;

        default:
            switch (status & 0xF0) {
            case NoteOn:
                eventType = NoteOn;
                break;
            case NoteOff:
                eventType = NoteOff;
                break;
            case NoteAftertouch:
                eventType = NoteAftertouch;
                break;
            case Controller:
                eventType = Controller;
                break;
            case ProgramChange:
                eventType = ProgramChange;
                break;
            case ChannelAftertouch:
                eventType = ChannelAftertouch;
                break;
            case PitchBend:
                eventType = PitchBend;
                break;

            default:
                throw FileException("Fichero incompatible. Evento desconocido");
            }
        }

        // Creacion del evento

        event = new Event(eventType, deltaTime);

        switch (eventType) {
        case Meta:
            event->setMetaEventType(metaEventType);

        case SysEx:
        case SysExCont:
            event->setData(data, dataLength);
            break;

        default:
            event->setMidiChannel(midiChannel);

            switch (eventType) {
            case NoteOn:
            case NoteOff:
                event->setNoteNumber(param1);
                event->setVelocity(param2);
                break;

            case NoteAftertouch:
                event->setNoteNumber(param1);
                event->setAmount(param2);
                break;

            case Controller:
                event->setControllerType(param1);
                event->setControllerValue(param2);
                break;

            case ProgramChange:
                event->setProgramNumber(param1);
                break;

            case ChannelAftertouch:
                event->setAmount(param1);
                break;

            default: // PitchBend
                event->setPitchBend(param1, param2);
            }
        }

        events.push_back(event);
        nTicks += deltaTime;

    }    while (!endOfTrack);

    delete[] data;
}
